package tree;

import geom.Geom;
import geom.PolarVector;
import geom.Vector;

import java.util.ArrayList;
import java.util.Iterator;

import processing.core.PApplet;


public class AbsorbtionBranch {
	static int currentId = 0;
	int id;
	
	int age;
	
	private PolarVector length;
	float weight;
	
	AbsorbtionBranch parent;
	ArrayList<AbsorbtionBranch> branches;
	Vector parentPos;
	private float currentEnergy;
	private float endArea;
	//float startArea;
	private Vector tempPos = null;
	
	public AbsorbtionBranch(AbsorbtionBranch parent, Vector parentPos, PolarVector length, float weight){
		this.parent = parent;
		branches = new ArrayList<AbsorbtionBranch>();
		this.weight = weight;
		this.length = length;
		currentEnergy = 0;
		endArea = 1;
		currentId++;
		id = currentId;
		age = 0;
		this.parentPos = parentPos;
	}
	
	//Genome
	float absorbtionFactor = 0.5f;
	float diffusion = 0.01f;
	float branchChance = 0.01f;
	boolean lengthFirst = true;
	float lengthAreaRatio = 0.5f;
	float minAngleDev = (float)Math.PI / 4f;
	float newBranchAngleDev = (float)Math.PI / 2;
	float energyToNewBranch = 0.1f;
	float branchThreshold = 20f;
	float newBranchEnergy = 100f;
	//!Genome
	
	//environment factors
	float growthCost = 1f;
	float newBranchCost = (float)Math.pow(2, 3);
	float growthAgeDecay = 0.2f;
	float growthDecayPow = 2f;
	//!environment factors
	public void absorb(float energy){
		//PApplet.println("volume " + volume());
		float absorbedEnergy = 2f *  energy * volume() / (branchVolume() + rootVolume() - volume()) * absorbtionFactor;
		absorbedEnergy /= (float)Math.pow(1 + age * growthAgeDecay, growthDecayPow);
		
		currentEnergy += absorbedEnergy;
		if(parent == null) absorbedEnergy *= 0.5f; // root generation
		float energyRemaining = energy - absorbedEnergy;
		System.out.println("bid" + id + " absorbed " + currentEnergy + " out of " + energy + " energy units" );
		if(branches.size() > 0){
			Iterator<AbsorbtionBranch> bit = branches.iterator();
			
			while(bit.hasNext()){
				AbsorbtionBranch next = bit.next();
				next.absorb(energyRemaining * next.weight);
			}
		} else if(energyRemaining >= newBranchCost){
			extend(energyRemaining);
		}
	}
	
	float numberOfBranchesX = 1.9f;
	float numberOfBranchesN = 4f;
	private void extend(float energy) {
		PApplet.println("extend" + energy);
		int newBranches = (int)Math.ceil(Math.pow((Math.random()*numberOfBranchesX), numberOfBranchesN));
		PApplet.println("extend" + branches + " branches with" + energy + " energy");
		float weightRemaining = 1;
		for(int i = 1; i < newBranches; i++){
			weightRemaining = createBranch(weightRemaining, energy, 0.33f);
		}
		weightRemaining = createBranch(weightRemaining, energy, 0.33f);
	}
	
	public float createBranch(float weightRemaining, float energy, float heightCoverage){
		float weight = (float)Math.random()*weightRemaining;
		float dev = (float)Math.random() * newBranchAngleDev - newBranchAngleDev/2;
		if(Math.random() > 0.5) dev *= -1;
		PolarVector l = new PolarVector(dev, 0 , 1);
		Vector p = new Vector(1f - (float)Math.random() * heightCoverage, 1f - (float)Math.random(), 0);
		PApplet.println("parentPos:" + p.toString());
		AbsorbtionBranch b = new AbsorbtionBranch(this, p, l, weight);
		b.currentEnergy = energy * weight;
		weightRemaining -= weight;
		branches.add(b);
		return weightRemaining;
	}
	
	
	
	
	float lengthGrowthDecay = 0.2f;
	float ageDecay = 0.0f;
	public void grow(){
		float growth = (currentEnergy / (float)Math.pow(1 + age * ageDecay, 2)) / growthCost;
		//float lengthGrowth = growth / (1 + ((float)Math.sqrt(length()) * lengthGrowthDecay));
		//float widthGrowth = growth - lengthGrowth;
		float lGrowth = 2f * (float)Math.pow(growth, 1f/3f)  / (1 + (float)Math.sqrt(length()) * lengthGrowthDecay);
		addDist(lGrowth);
		endArea += 1 * Math.pow(growth, 1f/3f);
		//a2 = 2 * (v + growth) / l - a1;
		//endArea = 2 * (volume() + widthGrowth) / length() - startArea();
		Iterator<AbsorbtionBranch> bit = branches.iterator();
		while(bit.hasNext()){
			bit.next().grow();
		}
		currentEnergy = 0;
		age++;
	}
	public PolarVector getLength(){
		return length;
	}
	public PolarVector getVector(){
		return getLength();
	}
	public float getDist(){
		return getLength().d;
	}
	public float getA(){
		return getLength().a;
	}
	public float getE(){
		return getLength().e;
	}

	
	public void setVector(PolarVector pv){
		length = pv;
		clearCachedPosition();
	}
	public void setDist(float f){
		length.d = f;
		clearCachedPosition();
	}
	public void setA(float f){
		length.a = f;
		clearCachedPosition();
		}
	public void setE(float f){
		length.e = f;
		clearCachedPosition();
	}
	public void addDist(float f){
		length.d += f;
		clearCachedPosition();
	}
	public void addA(float f){
		length.a += f;
		clearCachedPosition();
		}
	public void addE(float f){
		length.e += f;
		clearCachedPosition();
	}

	public float getEndArea() {
		return endArea;
	}

	public void setEndArea(float endArea) {
		this.endArea = endArea;
		clearCachedPosition();
	}
	public void clearCachedPosition(){
		if(this.tempPos == null) return;
		this.tempPos = null;
		Iterator<AbsorbtionBranch> bit = this.branchIter();
		if(bit == null) return;
		while(bit.hasNext()) bit.next().clearCachedPosition();
	}

	
	public float meanArea(){
		return (startArea() + endArea) / 2;
	}
	
	

	
	public float getWeight(){
		return weight;
	}
	
	public void getWeightFor(AbsorbtionBranch child){
		if(branches.contains(child)){
			
		}
	}
	
	public float startArea(){
		if(parent == null) return endArea;
		return parent.endArea * weight;
	}
	




	public float length(){
		return length.d;
	}
	
	public void draw(PApplet pa){
		pa.pushMatrix();	
		pa.translate(position().x, position().y);
		pa.rotate(length.a);
		//float wp = PApplet.sqrt(parent.getEndArea());
		float w1 = PApplet.sqrt(startArea());
		float w2 = PApplet.sqrt(getEndArea());
		pa.beginShape();
		pa.vertex(0, -w1 / 2);
		pa.vertex(0, w1 / 2);
		pa.vertex(length(), w2 / 2);
		pa.vertex(length(), -w2 / 2);
		pa.endShape(PApplet.CLOSE);
		pa.popMatrix();
		Iterator<AbsorbtionBranch> bit = branches.iterator();

		
		while(bit.hasNext()){
			AbsorbtionBranch next = bit.next();
			next.draw(pa);
		}
	}

	
	
	public float volume(){
		return (startArea() + endArea) / 2 * length();
	}
	
	public float branchVolume(){
		float volume = volume();
		Iterator<AbsorbtionBranch> bit = branches.iterator();
		while(bit.hasNext()){
			volume += bit.next().branchVolume();
		}
		return volume;
	}
	
	Iterator<AbsorbtionBranch> iterator;
	public void initBranchIterator(){
		iterator = branches.iterator();
	}
	public Iterator<AbsorbtionBranch> branchIter(){
		return iterator;
	}
	
	
	public float rootLength(){
		if(parent == null) return length();
		return parent.rootLength();
	}
	public float rootVolume(){
		float volume = volume();
		if(parent == null) return volume;
		return volume + parent.rootVolume();
	}
	public Vector position(){
		if(this.tempPos != null) return tempPos;
		if(parent == null) tempPos =  new Vector();
		else tempPos = parent.position(this);
		return tempPos;
	}
	
	public Vector position(AbsorbtionBranch b){
		
		int i = branches.indexOf(b);
		if(i == -1) return new Vector();
		
		float w = (float) Math.sqrt(getEndArea());
		Vector pos = position();
		Vector size = new Vector(getDist(), w, 0);
		Vector offset = size.mult(b.parentPos);
		
		offset.y -= w / 2f;
		PApplet.println("offsetabs:"+offset.absLength());
		offset.rotate(getA(), new Vector(0, 0, 0));
		PApplet.println("offsetabs:"+offset.absLength());
		
		PApplet.println("size: " + size.toString());
		if(parentPos != null)PApplet.println("parentPos: " + parentPos.toString());
		PApplet.println("offset: " + offset.toString());
		PApplet.println("length " + getDist() + " w: " + w);
		return pos.add(offset);
		
		
		
		
		
		
		
		
		
		
		
 		//float offset = (float)i / (float)branches.size() * w - w / 2f;
		//float a = getLength().a + (float)Math.PI / 2f;
		//return new PolarVector(a, getLength().e, offset).getCartesian().add(pos);
	}


}
